<?php

class KVStore {
	protected $tblname, $idx_tblname;
	
	function __construct($tblname, $id_sql_type = 'VARCHAR(100)') {
		$this->tblname = $tblname;
		$this->idx_tblname = $tblname . '_index';
		//$this->id_sql_type = $idsize ? 'VARCHAR(' . (int)$idsize . ')' : 'INTEGER AUTO_INCREMENT';	
		$this->id_sql_type = $id_sql_type;

		$x = explode(' ', $id_sql_type);
		$this->id_sql_only_type = $x[0];
		
		db_query("SHOW TABLES LIKE '{{$this->tblname}}'");
		if (db_affected_rows()) return;
		$rs = db_query("
			CREATE TABLE {{$this->tblname}} (
				id {$this->id_sql_type} PRIMARY KEY,
				uid INT,
				gid INT,
				mode INT,
				created INT,
				changed INT,
				serialized TINYINT,
				compressed TINYINT,
				value LONGBLOB
			)
		");
		if ($rs === FALSE) { echo "\nFATAL ERROR!\n"; die; }
		$rs = db_query("
			CREATE TABLE {{$this->idx_tblname}} (
				id {$this->id_sql_only_type},
				path VARCHAR(128),
				delta SMALLINT DEFAULT 0,
				i INT,
				s VARCHAR(128) CHARACTER SET utf8,
				PRIMARY KEY (id, path, delta),
				INDEX i1 (s),
				INDEX i2 (i),
				INDEX i3 (path, s),
				INDEX i4 (path, i)
			)
		");
		
	}

	function reset() {
		db_query("TRUNCATE TABLE {{$this->tblname}}");
		db_query("TRUNCATE TABLE {{$this->idx_tblname}}");
	}

	function &get($id, $default_value = NULL) {
		return $this->load($id, $default_value);
	}

	function del($id) {
		db_query("DELETE FROM {{$this->idx_tblname}} WHERE id = '%s'", $id);
		db_query("DELETE FROM {{$this->tblname}} WHERE id = '%s'", $id);		
		return db_affected_rows() ? TRUE : FALSE;
	}

	
	function set($id, &$value = NULL) {
		return $value === NULL ? $this->del($id) : $this->save($value, $id);
	}
	
	function &load($id, $default_value = NULL) {
		$rs = db_query("SELECT * FROM {{$this->tblname}} WHERE id = '%s'", $id);
		$row = db_fetch_object($rs);
		if (!$row)
			return $default_value;
			
		$compressed = (int)$row->compressed;
		$serialized = (int)$row->serialized;
		$value = $row->value;
		
		if ($compressed)
			$value = gzuncompress($value);
		if ($serialized)
			$value = unserialize($value);
		
		return $value;
	}
	
	function save(&$value, $id = NULL) {
		$compressed = 0;
		$serialized = 0;

		if (is_scalar($value)) {
			$v = (string)$value;
		} else {
			if (is_object($value) && isset($value->__id)) {
				if ($id === NULL)
					$id = $value->__id;
				unset($value->__id);
			}
			if (is_array($value) && isset($value['__id'])) {
				if ($id === NULL)
					$id = $value['__id'];
				unset($value['__id']);
			}
			$v = serialize($value);
			$serialized = 1;
		}
		
		//var_dump($v);
		
		if (strlen($v) > 1000) {
			$v = gzcompress($v, 1);
			$compressed = 1;
		}
		
		$t = time();
		
		global $user;
		
		if (db_result(db_query("SELECT 1 FROM {{$this->tblname}} WHERE id = '%s'", $id))) {	
			$rs = db_query("
				UPDATE {{$this->tblname}}
				SET compressed = %d, serialized = %d, value = '%s', changed = %d
				WHERE id = '%s'
			", $compressed, $serialized, $v, $t, $id);
		} else {
			if ($id !== NULL) {
				$rs = db_query("
					INSERT INTO {{$this->tblname}} (id, compressed, serialized, value, created, uid)
					VALUES ('%s', %d, %d, '%s', %d, %d)
				", $id, $compressed, $serialized, $v, $t, $user->uid);
			} else {
				$rs = db_query("
					INSERT INTO {{$this->tblname}} (compressed, serialized, value, created, uid)
					VALUES (%d, %d, '%s', %d, %d)
				", $compressed, $serialized, $v, $t, $user->uid);
			}
	
			if ($rs === FALSE)
				return FALSE;
	
			if ($id === NULL)
				$id = db_last_insert_id();
		}

		if (is_object($value))
			$value->__id = $id;
			
		if (is_array($value))
			$value['__id'] = $id;

		$this->update_index($value, $id);

		return $id;
	}

	
	function update_index(&$x, $id) {
		$out = array();
		$xx = array(array('', $x));
		$locked = array();
		$locked[] = $x;
		$n = 1;
		$i = 0;
		$cnt = 0;
		for (; $i < $n; ++$i) {
			if (++$cnt > 1000) {
				echo "hand up\n";
				die;
			}
			$path = $xx[$i][0];
			$val =& $xx[$i][1];
			if (is_scalar($val))
				$out[] = array($path, substr($val, 0, 50));
			else {
				if (is_object($val)) {
					if ($val->__locked) continue;
					$val->__locked = TRUE;
					$locked[] = $val;
				}
				if (is_array($val)) {
					if ($val['__locked']) continue;
					$val['__locked'] = TRUE;
					$locked[] = $val;
				}
				foreach ($val as $k =>& $v) {
					if (substr($k, 0, 2) == '__') continue;
					//$xx[$n++] = array($path ? $path . '/' . $k : $k, $v);
					$xx[$n++] = array($path . '/' . $k, $v);
				}
			}
		}
		foreach ($locked as &$val) {
			if (is_object($val))
				unset($val->__locked);
			else if (is_array($val))
				unset($val['__locked']);
		}
		
			
		db_query("DELETE FROM {{$this->idx_tblname}} WHERE id = '%s'", $id);
		
		if (count($out) > 0) {
			$ins = array();
			foreach ($out as $x) {
				$i = 0;
				$s = $x[1];
				if ('' . (int)$s == $s) {
					$i = (int)$s;
					$s = '';
				}
				$ins[] = sprintf("('%s', '%s', %d, '%s')", $id, $x[0], $i, $s);
			}
			db_query("INSERT INTO {{$this->idx_tblname}} (id, path, i, s) VALUES \n" . join(",\n", $ins));
		}
	}


}






